#include <errno.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
//#include <wchar.h>
#include <string.h>

#include <aura.h>
#include <auraplus.h>
#include <linknode.h>
#include <aura_wchar.h>


//using namespace Elastos;


PDoAtExceptionalExitOfProcess s_pDoAtExceptionalExitFunc = AURA_NULL;

Aura_ProcessId g_processId = 0;

static Aura_WChar s_wszCommandLine[AURA_MAX_PATH];

extern void _AllThread_Kill();
EXTERN void _AllThread_SetPriority(Aura_ThreadPriorityRank PriorityRank);
EXTERN void _AllThread_GetPriority(Aura_ThreadPriorityRank *pPriorityRank);

AURA_API AuraProcess_GetCurrentId(
        /* [out] */ Aura_ProcessId * pProcessId)
{
    if(AURA_NULL == pProcessId){
        PERROR("Error: Get Current Process Id failed! \
            file:%s. line:%d. \n",
            __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    //Each thread can get id with getpid().
    //g_processId usually is setted at aura init.
    assert(0 != g_processId);

    *pProcessId = g_processId;

    return AURA_NOERROR;
}

AURA_API_(void) AuraProcess_Exit(
        /* [in] */ Aura_ExitCode exitCode)
{
    //_AllThread_Kill();
    exit(exitCode);
}

AURA_API_(Aura_PWChar) AuraProcess_GetCommandLine()
{
    Aura_ProcessId pid;
    char szProcCmdLine[AURA_MAX_PATH];
    char szCmdLine[AURA_MAX_PATH];
    int fd;
    int iReadSize;
    Aura_ECode ec;
    AuraProcess_GetCurrentId(&pid);

    sprintf(szProcCmdLine, "/proc/%d/cmdline", (int)pid);

    fd = open(szProcCmdLine, O_RDONLY);
    if(-1 == fd){
        PERROR("Error: Get Command Line failed! errno%d. \
        file:%s. line:%d. \n",
        errno, __FILE__, __LINE__);
        ec =  AURA_E_INVALID_OPERATION;
        return AURA_NULL;
    }

    memset(szCmdLine, 0, AURA_MAX_PATH);

    iReadSize = read(fd, szCmdLine, AURA_MAX_PATH);
    if(-1 == iReadSize){
        PERROR("Error: Get Memory information failed! errno%d. \
        file:%s. line:%d. \n",
        errno, __FILE__, __LINE__);
        close(fd);
        ec = AURA_E_INVALID_OPERATION;
        return AURA_NULL;
    }
    close(fd);

    for(int i = 0; i < (iReadSize - 1); i++){
        if(0 == szCmdLine[i]){
            szCmdLine[i] = ' ';
        }
    }
    //TODO:Remove the "loader.exe", and debug "-D"
    PTRACE("szCmdLine:%s. iReadSize:%d.\n", szCmdLine, iReadSize);
    char *p = strstr(szCmdLine, "loader.exe");
    if(NULL != p){
        p += (strlen("loader.exe") + 1);
    } else {
        p = szCmdLine;
    }
    PTRACE("s_wszCommandLine:%s.\n", p);

    AnsiToUnic(s_wszCommandLine, p);

    return s_wszCommandLine;
}

AURA_API AuraProcess_Create(
        /* [in]  */ Aura_PWChar imageName,
        /* [in]  */ Aura_PWChar arguments,
        /* [out] */ Aura_Handle * phProcess,
        /* [out] */ Aura_ProcessId * pProcessId)
{
    pid_t pid;
    char pszName[AURA_MAX_PATH];
    char pszArguments[AURA_MAX_PATH];
    int execRet;

    if(imageName == AURA_NULL){
        PERROR("Error: Process Create failed! \
            file:%s. line:%d. \n",
            __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    UnicToAnsi(pszName, (const Aura_PWChar)imageName);
    PTRACE("Process Create imageName:%s.\n", pszName);

    if(!arguments == AURA_NULL){
        UnicToAnsi(pszArguments, (const Aura_PWChar)arguments);
        PTRACE("Process Create arguments:%s.\n", pszArguments);
    }

    pid = fork();

    if(pid < 0){
        PERROR("Error: Aura Process Create failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
    }else if (0 == pid){
        //Have in sub process
        execRet = execlp("./loader.exe", pszName, pszArguments, AURA_NULL);
        if(execRet < 0){
            PERROR("Error: execlp failed! Error number:0x%d. \
            file:%s. line:%d. \n",
            errno, __FILE__, __LINE__);
            exit(errno);
        }
        //Should not access here!
        exit(0xFE);
    }

    if(AURA_NULL != phProcess){
        *phProcess = (Aura_Handle)pid;
    }
    if(AURA_NULL != pProcessId){
        *pProcessId = (Aura_ProcessId)pid;
    }

    return AURA_NOERROR;
}

AURA_API AuraProcess_Open(
        /* [in] */ Aura_ProcessId processId,
        /* [out] */ Aura_Handle * phProcess)
{
    Aura_ECode ec = AURA_NOERROR;

    if((0 == processId) ||(AURA_NULL == phProcess)){
        PERROR("Error: Process Open failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    *phProcess = (Aura_Handle)processId;

    return ec;
}


AURA_API AuraProcess_Kill(
        /* [in] */ Aura_Handle hProcess,
        /* [in] */ Aura_ExitCode exitCode)
{
    int iIsKill;

    if(AURA_NULL == hProcess){
        PERROR("Error: Process Kill failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    iIsKill = kill((pid_t)hProcess, SIGKILL);
    if(-1 == iIsKill){
        PERROR("Error: Process Kill failed! errno:%d.\
        file:%s. line:%d. \n",
        errno, __FILE__, __LINE__);
        return AURA_E_INVALID_OPERATION;
    }

    return AURA_NOERROR;
}



//Destroy some private info
AURA_API AuraProcess_Destroy(
        /* [in] */ Aura_Handle hProcess)
{

    return AURA_NOERROR;
}

AURA_API AuraProcess_GetPriorityRank(//current
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_ThreadPriorityRank * pPriorityRank)
{

    _AllThread_GetPriority(pPriorityRank);
    return AURA_NOERROR;
}

AURA_API AuraProcess_SetPriorityRank(//current
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_ThreadPriorityRank priorityRank)
{
    _AllThread_SetPriority(priorityRank);
    return AURA_NOERROR;
}

AURA_API AuraProcess_GetLifespan(
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_SystemTime * pStartTime,
        /* [out] */ Aura_SystemTime * pExitTime)
{
    return AURA_E_NOT_IMPLEMENTED;
}

AURA_API AuraProcess_GetProcessorTime(//current
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_Millisecond * pUserTime,
        /* [out] */ Aura_Millisecond * pPrivilegedTime)
{
    struct rusage usage;
    int ret;

    if((AURA_NULL == pUserTime)
        || (AURA_NULL == pPrivilegedTime))
    {
        PERROR("Error: Get Process Processor Time failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    ret = getrusage(RUSAGE_SELF, &usage);
    if(-1 == ret){
        PERROR("Error: Get Process Processor Time failed! error number:%d.\
        errno, file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_OPERATION;
    }

    *pUserTime = usage.ru_utime.tv_sec*1000 + usage.ru_utime.tv_usec/1000;
    *pPrivilegedTime = usage.ru_stime.tv_sec*1000 + usage.ru_stime.tv_usec/1000;

    return AURA_NOERROR;
}

AURA_API AuraProcess_GetExitCode(
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_ExitCode * pExitCode)
{
    pid_t pid;
    pid_t ret;
    int iStatloc;

    if((AURA_NULL == pExitCode)||(AURA_NULL == hProcess)){
        PERROR("Error: Get Exit Code failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    pid = (pid_t)hProcess;
    ret = waitpid(pid, &iStatloc, WNOHANG);
    if(0 == ret){
        return AURA_E_PROCESS_STILL_ACTIVE;
    }

    //We just get the exception process
    *pExitCode = -1;

    return AURA_NOERROR;
}

AURA_API AuraProcess_GetMemoryInformation(
        /* [in] */ Aura_Handle hProcess,
        /* [out] */ Aura_Int32 * pPrivateVirtualMemorySize,
        /* [out] */ Aura_Int32 * pSharedVirtualMemorySize,
        /* [out] */ Aura_Int32 * pPrivatePhysicalMemorySize,
        /* [out] */ Aura_Int32 * pSharedPhysicalMemorySize)
{
    char szProcStatm[AURA_MAX_PATH];
    char szStatm[AURA_MAX_PATH];
    int iReadSize;
    int fdStatm;
	int iPhysicalPages = 0;
	int iPhysicalResident = 0;
	int iPhysicalShare = 0;
	int iPhysicalTrs = 0;
	int iPhysicalLrs = 0;
	int iPhysicalDrs = 0;
	int iPhysicalDt = 0;

    if(AURA_NULL == hProcess){
        PERROR("Error: Get Memory information failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    sprintf(szProcStatm, "/proc/%d/statm", (int)hProcess);

    fdStatm = open(szProcStatm, O_RDONLY);
    if(-1 == fdStatm){
        PERROR("Error: Get Memory information failed! errno%d. \
        file:%s. line:%d. \n",
        errno, __FILE__, __LINE__);
        return AURA_E_INVALID_OPERATION;
    }

    memset(szStatm, 0, AURA_MAX_PATH);

    iReadSize = read(fdStatm, szStatm, AURA_MAX_PATH);
    if(-1 == iReadSize){
        PERROR("Error: Get Memory information failed! errno%d. \
        file:%s. line:%d. \n",
        errno, __FILE__, __LINE__);
        close(fdStatm);
        return AURA_E_INVALID_OPERATION;
    }

    PTRACE("szStatm:%s\n", szStatm);

    sscanf(szStatm, "%d %d %d %d %d %d %d\n",
        &iPhysicalPages,
        &iPhysicalResident,
        &iPhysicalShare,
        &iPhysicalTrs,
        &iPhysicalLrs,
        &iPhysicalDrs,
        &iPhysicalDt);

    if(AURA_NULL != pPrivateVirtualMemorySize){
        *pPrivateVirtualMemorySize
            = (iPhysicalPages - iPhysicalShare) * _PAGE_SIZE;
    }
    if(AURA_NULL != pSharedVirtualMemorySize){
        *pSharedVirtualMemorySize = iPhysicalShare * _PAGE_SIZE;
    }
    if(AURA_NULL != pPrivatePhysicalMemorySize){
        *pPrivatePhysicalMemorySize
            = (iPhysicalPages - iPhysicalShare) * _PAGE_SIZE;
    }
    if(AURA_NULL != pSharedPhysicalMemorySize){
        *pSharedPhysicalMemorySize = iPhysicalShare * _PAGE_SIZE;
    }

    close(fdStatm);

    return AURA_NOERROR;
}

void GetProcessExitInfo(int arg)
{
    pid_t pid;
    int status;

    pid = waitpid(-1, &status, WNOHANG);
    if(pid <= 0){
        PERROR("Error: wait failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return;
    }
    //BUGBUG:May take some pid which is no elastos.
    if(AURA_NULL != s_pDoAtExceptionalExitFunc){
        s_pDoAtExceptionalExitFunc((Aura_ProcessId)pid);
    }

}

AURA_API AuraCentralContext_Set(
    /* [in] */ PDoAtExceptionalExitOfProcess  pDoAtExtFunc)
{
    Aura_ECode ec = AURA_NOERROR;

    if(SIG_ERR == signal(SIGCHLD, GetProcessExitInfo)){
        PERROR("Error: Set Central Context failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_OPERATION;
    }

    s_pDoAtExceptionalExitFunc = (PDoAtExceptionalExitOfProcess)pDoAtExtFunc;

    return ec;
}

AURA_API_(void) AuraCentralContext_Clear()
{
    if(SIG_ERR == signal(SIGCHLD, SIG_DFL)){
        PERROR("Error: Clear Central Context failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
    }
}

//#define DEBUG_AURA_PROCESS
#ifdef DEBUG_AURA_PROCESS
int main(int argc, char argv[])
{
    WString wImageName;
    WString wArguments;
    Aura_Handle hProcess = AURA_NULL;
    Aura_ProcessId iProcessId = 0;
    Aura_ProcessId CurrentProcessId = 0;
    Aura_ECode ec;
    Aura_Millisecond pUserTime;
    Aura_Millisecond pPrivilegedTime;

    wImageName = L"loop.exe";
    wArguments = L"";

    AuraCentralContext_Set((Aura_Address)0);

    ec = AuraProcess_Create(wImageName, wArguments,
        AURA_NULL, &iProcessId);

    getchar();

    printf("In aura process.\n");

    AuraProcess_GetCurrentId(&CurrentProcessId);
    printf("CurrentProcessId:%d\n", CurrentProcessId);

    AuraProcess_GetProcessorTime(AURA_NULL, &pUserTime, &pPrivilegedTime);
    printf("CurrentProcess pUserTime:%d\n", pUserTime);
    printf("CurrentProcess pPrivilegedTime:%d\n", pPrivilegedTime);

    printf("Get the child process info\n");
    AuraProcess_Open(iProcessId, &hProcess);

    Aura_Int32 PrivateVirtualMemorySize;
    Aura_Int32 SharedVirtualMemorySize;
    Aura_Int32 PrivatePhysicalMemorySize;
    Aura_Int32 SharedPhysicalMemorySize;

    ec = AuraProcess_GetMemoryInformation(
    hProcess,
    &PrivateVirtualMemorySize,
    &SharedVirtualMemorySize,
    &PrivatePhysicalMemorySize,
    &SharedPhysicalMemorySize);
    if(AURA_FAILED(ec)){
        PERROR("Error: AuraProcess_GetMemoryInformation failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
    }

    printf("PrivateVirtualMemorySize:%d.\n", PrivateVirtualMemorySize);
    printf("SharedVirtualMemorySize:%d.\n", SharedVirtualMemorySize);
    printf("PrivatePhysicalMemorySize:%d.\n", PrivatePhysicalMemorySize);
    printf("SharedPhysicalMemorySize:%d.\n", SharedPhysicalMemorySize);

    printf("Kill the child process.\n");
    getchar();
    ec = AuraProcess_Kill(hProcess, 0);
   if(AURA_FAILED(ec)){
        PERROR("Error: AuraProcess_Kill failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
    }

    printf("Get the child process exitcode\n");
    getchar();
    Aura_ExitCode iExitCode;
    AuraProcess_GetExitCode(hProcess, &iExitCode);

    printf("Clean info and exit.\n");
    getchar();
    AuraCentralContext_Clear();
    printf("End aura process. \n");
    AuraProcess_Exit(0);
    printf("NOT End aura process. \n");
    return (int)ec;
}

#endif

